const compileUtil = {
    //* 获取实例上data的数据
    getValue(expr, vm) {
        //* {{person.msg.xxx}} ['person','msg','xxx']
        //*  vm.$data['person']['msg']['xxx']
        return expr.split(".").reduce((data, current) => {
            return data[current];
        }, vm.$data);
    },
    //* 更新节点
    update: {
        updateHtml(node, value) {
            node.innerHTML = value;
        },
        updateText(node, value) {
            node.textContent = value;
        },
        updateModel(node, value) {
            node.value = value;
        },
        updateAttr(node, value, attrName) {
            node.setAttribute(attrName, value);
        },
    },

    html(node, expr, vm) {
        //* expr 指令的值
        //* 获取实例data上的数据并更新节点
        const value = this.getValue(expr, vm);
        //* 创建watcher实例,相关参数和回调函数
        new Watcher(expr, vm, (newValue) => {
            this.update.updateHtml(node, newValue);
        });
        this.update.updateHtml(node, value);
    },
    text(node, expr, vm) {
        let value;
        //* 判断是v-text还是{{}}
        if (expr.indexOf("{{") !== -1) {
            //* temp来保存{{}}内的内容,传入watcher的回调函数
            let temp;
            //* args是包括捕捉组在内的匹配内容的数组,详见replace
            value = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
                temp = args[1];
                return this.getValue(args[1], vm);
            });
            new Watcher(temp, vm, (newValue) => {
                this.update.updateText(node, newValue);
            });
        } else {
            value = this.getValue(expr, vm);
            new Watcher(expr, vm, (newValue) => {
                this.update.updateText(node, newValue);
            });
        }
        this.update.updateText(node, value);
    },
    model(node, expr, vm) {
        const value = this.getValue(expr, vm);
        new Watcher(expr, vm, (newValue) => {
            this.update.updateModel(node, newValue);
        });
        //* 视图=>数据=>视图
        node.addEventListener("input", (e) => {
            expr.split(".").reduce((data, current, index, src) => {
                //* currentIndex当前current索引值,src源数组
                if (index === src.length - 1) {
                    //* 当current为数组最后一项时
                    data[current] = e.target.value;
                }
                return data[current];
            }, vm.$data);
        });
        this.update.updateModel(node, value);
    },
    on(node, expr, vm, eventName) {
        const fn = vm.$options.methods && vm.$options.methods[expr];
        //* 此处记得修改fn的this指向,不然this会指向node
        node.addEventListener(eventName, fn.bind(vm), false);
    },
    bind(node, expr, vm, attrName) {
        const value = this.getValue(expr, vm);
        //* 不要忘记传入修改的属性名
        new Watcher(expr, vm, (newValue) => {
            this.update.updateAttr(node, newValue, attrName);
        });
        this.update.updateAttr(node, value, attrName);
    },
};
class Compile {
    constructor(el, vm) {
        //* 根节点el为元素节点或字符串
        this.el = this.isNodeElement(el) ? el : document.querySelector(el);
        //* 保存vue实例
        this.vm = vm;
        //* 将根节点的子节点存入文档碎片对象,以减少回流
        const fragment = this.node2Fragment(this.el);
        //* 编译模板
        this.compile(fragment);
        //* 根节点的子元素经过编译,重新移动回根节点下
        this.el.appendChild(fragment);
    }
    compile(frag) {
        let childNodes = frag.childNodes;
        //* 遍历这些节点,根据其类型分别处理
        [...childNodes].forEach((e) => {
            if (this.isNodeElement(e)) {
                //* 处理元素节点
                this.compileElement(e);
            } else if (this.isTextElement(e)) {
                //* 处理文本节点
                this.compileText(e);
            }
            //* 如果节点还有子节点,继续进行以上过程(递归)
            if (e.childNodes && e.childNodes.length) {
                this.compile(e);
            }
        });
    }
    compileElement(node) {
        //* 处理元素节点
        //* 保存单个节点的属性数组,并遍历
        const attrs = node.attributes;
        [...attrs].forEach((e) => {
            //* 结构赋值,提取出每个属性的属性名和值
            const { name, value } = e;
            //* v-bind:xxx='yyy'  v-bind指令名,xxx指令参数,yyy指令绑定值
            //* 处理以v-开头的指令
            if (this.isDirective(name)) {
                //* 解构赋值,获取指令名称和指令参数
                const [, directive] = name.split("-");
                const [dirName, eventName] = directive.split(":");
                //* 定义一个处理不同指令的方法对象,根据不同指令调用
                //* 分别传入 有指令的节点,指令值,实例,和事件名(如果有的话)
                compileUtil[dirName](node, value, this.vm, eventName);
                //* 删除标签上的指令
                node.removeAttribute("v-" + directive);
            } else if (this.isEventName(name)) {
                //* 处理@开头的指令
                //* 获取事件名
                let [, eventName] = name.split("@");
                compileUtil["on"](node, value, this.vm, eventName);
            } else if (this.isAttrName(name)) {
                //* 处理:开头的绑定属性
                //* 获取指令参数
                let [, attrName] = name.split(":");
                compileUtil["bind"](node, value, this.vm, attrName);
            }
        });
    }
    compileText(node) {
        //* 获取{{xxx}}形式的节点内容
        const content = node.textContent;
        if (/\{\{(.+?)\}\}/.test(content)) {
            compileUtil["text"](node, content, this.vm);
        }
    }
    isDirective(name) {
        //* 是否以'v-'开头
        return name.startsWith("v-");
    }
    isEventName(name) {
        //* 是否以'@'开头
        return name.startsWith("@");
    }
    isAttrName(name) {
        //* 是否以':'开头
        return name.startsWith(":");
    }
    node2Fragment(el) {
        //* 将根节点的子元素移动到文档碎片中
        const frag = document.createDocumentFragment();
        let firstChild;
        while ((firstChild = el.firstChild)) {
            frag.appendChild(firstChild);
        }
        return frag;
    }
    isNodeElement(node) {
        //* 是否为元素节点
        return node.nodeType === 1;
    }
    isTextElement(node) {
        //* 是否为文本节点
        return node.nodeType === 3;
    }
}
//* 自定义一个vue类
class MVue {
    constructor(options) {
        //* 保存出入的属性
        this.$options = options;
        this.$el = options.el;
        this.$data = options.data;
        if (this.$el) {
            //* 1.定义一个观察者类,将data传入
            new Observer(this.$data);
            //* 2.定义编译类,并编译,传入根节点和vue实例
            new Compile(this.$el, this);
            //* $data代理
            this.proxyData(this.$data);
        }
    }
    proxyData(data) {
        for (const key in data) {
            Object.defineProperty(this, key, {
                get() {
                    return data[key];
                },
                set(newVal) {
                    data[key] = newVal;
                },
            });
        }
    }
}